﻿using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using UIOC.CodeAnalysis.Extensions;
using UIOC.CodeAnalysis.Helpers;
using UIOC.Observable.CodeAnalysis.Diagnostics;

namespace UIOC.Observable.CodeAnalysis.Analyzers;

[DiagnosticAnalyzer(LanguageNames.CSharp)]
internal sealed class ROWInterfaceAnalyzer : DiagnosticAnalyzer {

    private static void LogInfo(string message) {
        SourceOutputHelper.LogInfo(DiagnosticSeverity.Info, $"[{nameof(ROWInterfaceAnalyzer)}] {message}");
    }

    public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
        ImmutableArray.Create(ROWInterfaceBasesDiagnostic.Rule);

    public override void Initialize(AnalysisContext context) {
        context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
        context.EnableConcurrentExecution();
        context.RegisterSyntaxNodeAction(AnalyzeClassDeclaration, SyntaxKind.InterfaceDeclaration);
    }

    private void AnalyzeClassDeclaration(SyntaxNodeAnalysisContext context) {
        var compilation = context.SemanticModel.Compilation;
        var assembly = compilation.Assembly;

        if (assembly.IsIgnore()) {
            return;
        }

        if (context.Node is not InterfaceDeclarationSyntax interfaceDeclarationSyntax) {
            return;
        }

        var interfaceSymbol = context.SemanticModel.GetDeclaredSymbol(interfaceDeclarationSyntax);
        if (interfaceSymbol == null)
            return;

        var observableObjectInterfaceSymbol = compilation.GetTypeByMetadataName(TypeFullNames.IObservableObject);
        if (observableObjectInterfaceSymbol == null) {
            return;
        }

        var objectInterfaceSymbol = compilation.GetTypeByMetadataName(TypeFullNames.IObject);
        if (objectInterfaceSymbol == null) {
            return;
        }

        if (interfaceSymbol.ContainsInterface(objectInterfaceSymbol)) {
            var baseInterfaceSymbol = interfaceSymbol.Interfaces.Length > 0 ? interfaceSymbol.Interfaces[0] : null;
            var observableInterfaceSymbol = interfaceSymbol.Interfaces.Length > 1 ? interfaceSymbol.Interfaces[1] : null;

            if (baseInterfaceSymbol == null) {
                Report(context, null, interfaceDeclarationSyntax, true, objectInterfaceSymbol, 0);
            } else if (!ContainsAndNot(
                typeSymbol: baseInterfaceSymbol,
                requireTypeSymbols: [
                    objectInterfaceSymbol
                ],
                excludeTypeSymbols: null,
                out var baseFailedRequireTypeSymbolSet,
                out var baseFailedExcludeTypeSymbolSet
            )) {
                foreach (var typeSymbol in baseFailedRequireTypeSymbolSet) {
                    Report(context, baseInterfaceSymbol, interfaceDeclarationSyntax, true, typeSymbol, 0);
                }
                foreach (var typeSymbol in baseFailedExcludeTypeSymbolSet) {
                    Report(context, baseInterfaceSymbol, interfaceDeclarationSyntax, false, typeSymbol, 0);
                }
            }

            if (observableInterfaceSymbol == null) {
                Report(context, null, interfaceDeclarationSyntax, true, observableObjectInterfaceSymbol, 1);
            } else if (!ContainsAndNot(
                typeSymbol: observableInterfaceSymbol,
                requireTypeSymbols: [
                    observableObjectInterfaceSymbol,
                ],
                excludeTypeSymbols: null,
                out var observableFailedRequireTypeSymbolSet,
                out var observableFailedExcludeTypeSymbolSet
            )) {
                foreach (var typeSymbol in observableFailedRequireTypeSymbolSet) {
                    Report(context, observableInterfaceSymbol, interfaceDeclarationSyntax, true, typeSymbol, 1);
                }
                foreach (var typeSymbol in observableFailedExcludeTypeSymbolSet) {
                    Report(context, observableInterfaceSymbol, interfaceDeclarationSyntax, false, typeSymbol, 1);
                }
            }
        }
    }

    private void Report(
        SyntaxNodeAnalysisContext context,
        ITypeSymbol interfaceSymbol,
        InterfaceDeclarationSyntax interfaceDeclarationSyntax,
        bool isRequireOrExclude,
        ITypeSymbol failedTypeSymbol,
        int index
    ) {
        context.ReportDiagnostic(Diagnostic.Create(
            ROWInterfaceBasesDiagnostic.Rule,
            interfaceSymbol == null ? interfaceDeclarationSyntax.Identifier.GetLocation() : interfaceDeclarationSyntax.BaseList.Types[index].GetLocation(),
            interfaceDeclarationSyntax.GetDeclaraionName(),
            index + 1,
            interfaceSymbol == null ? string.Empty : $" '{interfaceSymbol.ToDisplayString(
                new SymbolDisplayFormat(
                    globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.Omitted,
                    typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypes,
                    genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters,
                    miscellaneousOptions:
                        SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers |
                        SymbolDisplayMiscellaneousOptions.UseSpecialTypes
                )
            )}' ",
            failedTypeSymbol.ToDisplayString(
                SymbolDisplayFormat.FullyQualifiedFormat.WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.Omitted)
            ),
            isRequireOrExclude ? "必须" : "不应"
        ));
    }

    private bool ContainsAndNot(
        ITypeSymbol typeSymbol,
        ITypeSymbol[] requireTypeSymbols,
        ITypeSymbol[] excludeTypeSymbols,
        out HashSet<ITypeSymbol> failedRequireTypeSymbolSet,
        out HashSet<ITypeSymbol> failedExcludeTypeSymbolSet
    ) {
        failedRequireTypeSymbolSet = new(SymbolEqualityComparer.Default);
        failedExcludeTypeSymbolSet = new(SymbolEqualityComparer.Default);
        if (requireTypeSymbols != null) {
            foreach (var requireTypeSymbol in requireTypeSymbols) {
                if (requireTypeSymbol != null) {
                    if (!SymbolEqualityComparer.Default.Equals(typeSymbol, requireTypeSymbol) && !typeSymbol.ContainsInterface(requireTypeSymbol)) {
                        failedRequireTypeSymbolSet.Add(requireTypeSymbol);
                    }
                }
            }
        }
        if (excludeTypeSymbols != null) {
            foreach (var excludeTypeSymbol in excludeTypeSymbols) {
                if (excludeTypeSymbol != null) {
                    if (SymbolEqualityComparer.Default.Equals(typeSymbol, excludeTypeSymbol) || typeSymbol.ContainsInterface(excludeTypeSymbol)) {
                        failedExcludeTypeSymbolSet.Add(excludeTypeSymbol);
                    }
                }
            }
        }
        return !failedRequireTypeSymbolSet.Any() && !failedExcludeTypeSymbolSet.Any();
    }
}